export default {
  name: "keep-alive",
  abstract: true, // 判断当前组件虚拟dom是否渲染成真是dom的关键

  props: {
    include: patternTypes, // 缓存白名单
    exclude: patternTypes, // 缓存黑名单
    max: [String, Number], // 缓存的组件实例数量上限
  },

  // 初始化两个对象分别缓存VNode（虚拟DOM）和VNode对应的键集合
  created() {
    this.cache = Object.create(null); // 用来保存需要缓存虚拟dom
    this.keys = []; // 用来保存缓存的虚拟dom的键集合
  },

  // 在mounted这个钩子中对include和exclude参数进行监听，然后实时地更新（删除）this.cache对象数据。pruneCache函数的核心也是去调用pruneCacheEntry
  mounted() {
    // 实时监听黑白名单的变动
    // 根据include和exclude参数变化来实时地更新this.cache对象数据
    this.$watch("include", (val) => {
      pruneCache(this, (name) => matches(val, name));
    });
    this.$watch("exclude", (val) => {
      pruneCache(this, (name) => !matches(val, name));
    });
    this.cacheVNode();
  },

  /*
    第一步：  获取了默认插槽组件的vnode
    第二步：  根据include/exclude判断是否需要缓存, 如果不需要, 直接返回vnode
    第三步：  如果vnode在缓存中已经存在了,
                取出缓存的组件实例,就不用再创建对应的实例
                删除缓存keys中的key, 再重新添加到最后 => 让当前操作的vNode的key缓存在数组的最后
              如果vnode在缓存中没有
                将vnode和key临时保存在组件对象
    第四步：  将该组件实例的keepAlive属性值设置为true
    第五步：  返回vnode
    其中用了LRU(Least Recently Used)算法
        通过keys数组来获取最近最少用的vNode: 最近用的放在keys的后面, 那用得最少就是第一个
  */
  render() {
    const slot = this.$slots.default;
    const vnode = getFirstComponentChild(slot);
    const componentOptions = vnode && vnode.componentOptions;
    if (componentOptions) {
      // 存在组件参数
      // check pattern
      const name = getComponentName(componentOptions); // 组件名
      const { include, exclude } = this;
      if (
        // 条件匹配
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode;
      }

      const { cache, keys } = this;
      const key =
        vnode.key == null // 定义组件的缓存key
          ? // same constructor may get registered as different local components
            // so cid alone is not enough (#3269)
            componentOptions.Ctor.cid +
            (componentOptions.tag ? `::${componentOptions.tag}` : "")
          : vnode.key;
      if (cache[key]) {
        // 已经缓存过该组件
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        remove(keys, key);
        keys.push(key); // 调整key排序
      } else {
        cache[key] = vnode; // 缓存组件对象
        keys.push(key);
        // prune oldest entry
        if (this.max && keys.length > parseInt(this.max)) {
          // 超过缓存数限制，将第一个删除
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }

      vnode.data.keepAlive = true; // 渲染和执行被包裹组件的钩子函数需要用到
    }
    return vnode || (slot && slot[0]);
  },
  updated: function () {
    this.cacheVNode();
  },
  methods: {
    cacheVNode: function () {
      /*
          如果有要缓存的vnode
            将当前vnode的组件实例对象缓存到cache中 (key为属性名, 值是组件对象)
            将对应的key保存到keys的最右边
            如果缓存数量超过max值, 删除keys中的第一个key对应缓存组件实例, 并销毁组件实例
            其中用了LRU(Least Recently Used)算法
              通过keys数组来获取最近最少用的vNode: 最近用的放在keys的后面, 那用得最少就是第一个
      */
      var _a = this,
        cache = _a.cache,
        keys = _a.keys,
        vnodeToCache = _a.vnodeToCache,
        keyToCache = _a.keyToCache;
      if (vnodeToCache) {
        var tag = vnodeToCache.tag,
          componentInstance = vnodeToCache.componentInstance,
          componentOptions = vnodeToCache.componentOptions;
        cache[keyToCache] = {
          name: _getComponentName(componentOptions),
          tag: tag,
          componentInstance: componentInstance,
        };
        keys.push(keyToCache);
        // prune oldest entry
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
        this.vnodeToCache = null;
      }
    },
  },
};

function pruneCacheEntry(cache, key, keys, current) {
  const cached = cache[key];
  if (cached && (!current || cached.tag !== current.tag)) {
    cached.componentInstance.$destroy(); // 执行组件的destory钩子函数
  }
  cache[key] = null;
  remove(keys, key);
}

/*
   keep-alive组件的渲染
    keep-alive它不会生成真正的DOM节点
    Vue在初始化生命周期的时候，为组件实例建立父子关系会根据abstract属性决定是否忽略某个组件。在keep-alive中，设置了abstract: true，
    那Vue就会跳过该组件实例。最后构建的组件树中就不会包含keep-alive组件，那么由组件树渲染成的DOM树自然也不会有keep-alive相关的节点了
    

  
  keep-alive包裹的组件是如何使用缓存的?
      因为keep-alive组件作为父组件，它的render函数会先于被包裹组件执行, 在首次加载被包裹组件时，由keep-alive.js中的render函数可知，
        会将需要缓存的组件虚拟DOM保存起来（this.cache和this.keys）,并将keepAlive的值设为true，
  再次访问被包裹组件时，就已经拿到保存的缓存的组件实例，这样就直接把上一次的DOM插入到了父元素中


*/
